//
// Created by user on 20-3-10.
//

#include "Printf.h"
#include "../MemoryManagement/Malloc.h"

char buffer[4096] = {0};

void frameBufferInit() {

    unsigned long i;
    unsigned long *tmpAddress;
    unsigned long *tmpAddress1;
    unsigned int *fontBufferAddr = (unsigned int *) Physical_To_Virtual(0xe0000000);

    // 1. 获取到当前 CR3 寄存器的值
    globalCR3 = getCR3();

    // 2. 获取到 PML4 的地址
    tmpAddress = Physical_To_Virtual((unsigned long *) ((unsigned long) globalCR3 & (~0xfffUL)) +
                              (((unsigned long) fontBufferAddr >> PAGE_GDT_SHIFT) & 0x1ff));

    // 3. 如果得到的 PML4 的页表项为 0，说明没有次级页表
    if (*tmpAddress == 0) {
        unsigned long *virtual = kmalloc(PAGE_4K_SIZE, 0);
        set_mpl4t(tmpAddress, mk_mpl4t(Virtual_To_Physical(virtual), PAGE_KERNEL_GDT));
    }

    // 4. 获取到 PDPT 的地址
    tmpAddress = Physical_To_Virtual(
            (unsigned long *) (*tmpAddress & (~0xfffUL)) + (((unsigned long) fontBufferAddr >> PAGE_1G_SHIFT) & 0x1ff));

    // 5. 如果得到的 PDPT 的页表项位 0，说明没有次级页表
    if (*tmpAddress == 0) {
        unsigned long *virtual = kmalloc(PAGE_4K_SIZE, 0);
        set_pdpt(tmpAddress, mk_pdpt(Virtual_To_Physical(virtual), PAGE_KERNEL_Dir));
    }

    // 6. 做最后的一级映射
    for (i = 0; i < displayInfo.fontBufferLength; i += PAGE_2M_SIZE) {

        tmpAddress1 = Physical_To_Virtual((unsigned long *) (*tmpAddress & (~0xfffUL)) +
                                   ((((unsigned long) fontBufferAddr + i) >> PAGE_2M_SHIFT) & 0x1ff));

        unsigned long phy = 0xe0000000 + i;
        set_pdt(tmpAddress1, mk_pdt(phy, PAGE_KERNEL_Page | PAGE_PWT | PAGE_PCD));

    }

    // 7. 更新帧缓存地址
    displayInfo.fontBufferAddr = (unsigned int *) Physical_To_Virtual(0xe0000000);

    // 8. 刷新CR3，让刚设置的页表项生效
    flushTLB();

}

int printfColor(unsigned int fontColor, unsigned int backgroundColor, const char *formatString, ...) {

    // 字符串的长度
    int i = 0;
    unsigned long flags = 0;
    int count = 0;
    int line = 0;

    // 可变参数变量
    va_list args;

    // 判断当前是不是中断程序在调用的
//    if (getRFLAGS() & 0x200UL) {
//        spinLock(&displayInfo.printkLock);
//    }
//    spinLock(&displayInfo.printkLock);
    spinLockIrqSave(&displayInfo.printkLock, flags);

    // 1. 开始
    va_start(args, formatString);

    // 2. 格式化字符串
    i = vsprintf(buffer, formatString, args);

    // 3. 结束读取可变参数
    va_end(args);

    // 4. 利用循环一个个输出字符
    for (count = 0; count < i || line; count++) {

        if (line > 0) {
            count--;
            goto Label_tab;
        }

        // 1> 如果是 '\n'，就说明现在要换行
        if ((unsigned char) *(buffer + count) == '\n') {

            // 光标的 X 置为0
            displayInfo.cursorPositionX = 0;

            // 光标的 Y 增加一行
            displayInfo.cursorPositionY++;

            // 2> 如果是 '\b'，就说明现在要回退一个字符
        } else if ((unsigned char) *(buffer + count) == '\b') {

            // 因为 '\b' 是回退的意思，所以光标位置X，要往后退一个字符
            displayInfo.cursorPositionX--;

            // 如果光标位置X小于0了，说明要换到上一行了
            if (displayInfo.cursorPositionX < 0) {

                displayInfo.cursorPositionX =
                        (displayInfo.resolutionX / displayInfo.charSizeWidth - 1) * displayInfo.charSizeWidth;
                displayInfo.cursorPositionY--;

                if (displayInfo.cursorPositionY < 0) {
                    displayInfo.cursorPositionY =
                            (displayInfo.cursorPositionY / displayInfo.charSizeHeight - 1) / displayInfo.charSizeHeight;
                }

            }

            printChar(displayInfo.fontBufferAddr, displayInfo.resolutionX,
                      displayInfo.cursorPositionX * displayInfo.charSizeWidth,
                      displayInfo.cursorPositionY * displayInfo.charSizeHeight, fontColor, backgroundColor, ' ');

            // 3> 如果是 '\t'，就说明现在要往前走8个字符
        } else if ((unsigned char) *(buffer + count) == '\t') {

            // ~(8 - 1) = 1，这时并就没有太大意思
            line = ((displayInfo.cursorPositionX + 8) & ~(8 - 1)) - displayInfo.cursorPositionX;
            Label_tab:
            line--;

            printChar(displayInfo.fontBufferAddr, displayInfo.resolutionX,
                      displayInfo.cursorPositionX * displayInfo.charSizeWidth,
                      displayInfo.cursorPositionY * displayInfo.charSizeHeight, fontColor, backgroundColor, ' ');

            displayInfo.cursorPositionX++;

            // 4> 如果没有的话就默认输出
        } else {

            printChar(displayInfo.fontBufferAddr, displayInfo.resolutionX,
                      displayInfo.cursorPositionX * displayInfo.charSizeWidth,
                      displayInfo.cursorPositionY * displayInfo.charSizeHeight, fontColor, backgroundColor,
                      (unsigned char) *(buffer + count));
            displayInfo.cursorPositionX++;

        }

        // 5> 判断是否超过屏幕X
        if (displayInfo.cursorPositionX >= (displayInfo.resolutionX / displayInfo.charSizeWidth)) {

            displayInfo.cursorPositionY++;

            displayInfo.cursorPositionX = 0;

        }

        // 6> 判断是否超过屏幕 Y
        if (displayInfo.cursorPositionY >= (displayInfo.resolutionY / displayInfo.charSizeHeight)) {

            unsigned int * addr1 = NULL;
            unsigned int * addr2 = NULL;

            // 版本1
//            int currentX, currentY;
//            for (int j = 0; j < (displayInfo.resolutionY / displayInfo.charSizeHeight); ++j) {
//
//                for (int k = 0; k < (displayInfo.resolutionX / displayInfo.charSizeWidth) - 1; ++k) {
//
//                    currentX = k * displayInfo.charSizeWidth;
//                    currentY = j * displayInfo.charSizeHeight;
//
//                    for (int l = 0; l < 16; l++) {
//
//                        // 1> 计算出当前要写入的帧缓存的首地址
//                        addr1 = displayInfo.fontBufferAddr + displayInfo.resolutionX * (currentY + l) + currentX;
//
//                        addr2 = displayInfo.fontBufferAddr + displayInfo.resolutionX * (currentY + l + displayInfo.charSizeHeight) + currentX;
//
//                        for (int h = 0; h < 8; h++) {
//
//                            *addr1 = *addr2;
//
//                            addr1++;
//                            addr2++;
//
//                        }
//
//                    }
//
//                }
//
//            }

            // 版本2
            addr1 = displayInfo.fontBufferAddr;
            addr2 = displayInfo.fontBufferAddr + displayInfo.resolutionX * displayInfo.charSizeHeight;

            for (int j = 0; j <= (displayInfo.resolutionX * displayInfo.resolutionY - displayInfo.resolutionX * displayInfo.charSizeHeight); ++j) {

                *addr1 = *addr2;
                addr1++;
                addr2++;

            }

            addr1 = displayInfo.fontBufferAddr + displayInfo.resolutionX * displayInfo.resolutionY - displayInfo.resolutionX * displayInfo.charSizeHeight;

            for (int k = displayInfo.resolutionX * displayInfo.resolutionY - displayInfo.resolutionX * displayInfo.charSizeHeight; k < displayInfo.resolutionX * displayInfo.resolutionY; ++k) {
                *addr1 = 0;
                addr1++;
            }

            displayInfo.cursorPositionY--;

//            displayInfo.cursorPositionY = 0;

        }

    }

//    if (getRFLAGS() & 0x200UL) {
//        spinUnlock(&displayInfo.printkLock);
//    }
//    spinUnlock(&displayInfo.printkLock);

    spinUnlockIrqRestore(&displayInfo.printkLock, flags);
    return i;

}

/**
 * 将字符串中一段数值字母的值读出来，例如："ada123da"，把123读出来（Linux源码中也有这个函数）
 * @param s 要读取的字符串，注意无法改变他的值
 * @return 返回读取出来的数值字母的值
 */
int skip_atoi(const char **s) {

    int i = 0;

    // i * 10 是因为每次读取都会进一次位，比如 个位，十位，百位，千位
    // (*s) 是获取这个字符串的地址
    // (*s)++ 是将这个字符串的地址前移一个字节
    // *((*s)++) 就是读出字符
    // 再减去 '0' 的ASCII的值，就是这个数值字符的大小了
    // 最后相加即可
    while (isDigit(**s)) {
        i = i * 10 + *((*s)++) - '0';
    }

    //while (1);

    return i;

}

/**
 * 将指定字符串的数值转换对应进制的字符串
 * @param str 待转换的字符串
 * @param num 要转换的数
 * @param scale 进制规格
 * @param fieldWidth 属性宽度
 * @param precision 精度
 * @param type 类型
 * @return 处理好的字符串
 */
char *number(char *str, long num, int scale, int fieldWidth, int precision, int type) {

    char c, sign, tmp[50] = {0};
    const char *digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    int i;

    // 1. 判断类型是否要输出小写字母
    if (type & SMALL) {
        digits = "0123456789abcdefghijklmnopqrstuvwxyz";
    }

    // 2. 判断类型是否 SUBTRACT ，如果是就对 ZEROPAD 取反然后与 type 并
    if (type & SUBTRACT) {
        type &= ~ZEROPAD;
    }

    if (scale < 2 || scale > 36) {
        return 0;
    }

    c = (char) ((type & ZEROPAD) ? '0' : ' ');

    sign = 0;

    if (type & SIGN && num < 0) {

        sign = '-';
        num = -num;

    } else {

        sign = (char) ((type & PLUS) ? '+' : ((type & SPACE ? ' ' : 0)));

    }

    if (sign) {

        fieldWidth--;

    }

    if (type && SPECIAL) {
        if (scale == 16) {
            fieldWidth -= 2;
        } else if (scale == 8) {
            fieldWidth--;
        }
    }

    i = 0;

    if (num == 0) {
        tmp[i++] = '0';
    } else {
        while (num != 0) {
            tmp[i++] = digits[do_div(num, scale)];
        }
    }

    if (i > precision) {
        precision = i;
    }

    fieldWidth -= precision;

    if (!(type & (ZEROPAD + SUBTRACT))) {
        while (fieldWidth-- > 0) {
            *str++ = ' ';
        }
    }

    if (sign) {
        *str++ = sign;
    }

    if (type & SPECIAL) {
        if (scale == 8) {
            *str++ = '0';
        } else if (scale == 16) {
            *str++ = '0';
            *str++ = digits[33];
        }
    }

    if (!(type & SUBTRACT)) {
        while (fieldWidth-- > 0) {
            *str++ = c;
        }
    }

    while (i < precision--) {
        *str++ = '0';
    }

    while (i-- > 0) {
        *str++ = tmp[i];
    }

    while (fieldWidth-- > 0) {
        *str++ = ' ';
    }

    return str;

}

/**
 * 将字符串格式化
 * @param buffer 字符串格式化后存放的缓冲区
 * @param formatString 需要格式化的字符串
 * @param args 可变参数
 * @return 格式化后字符串的长度
 */
int vsprintf(char *buffer, const char *formatString, va_list args) {

    // str 保存 buffer 的地址，s 是用来保存如果可变参数是字符串的时候
    char *str, *s;

    // 标志位
    int flags;

    // 属性的宽度，例如 %12.6x 中的12
    int fieldWidth;

    // 要打印的参数的精度，例如 %12.6x 中的 6
    int precision;

    // 这两个是输出字符串的时候使用
    int len, i;

    // 修饰符
    int qualifier;

    // *formatString 这里如果往后查询，是可以访问 0 的，这时循环就可以停止
    // formatString++ 这个是每次加一个字节，因为formatString是 char * 类型的指针变量
    for (str = buffer; *formatString; formatString++) {

        // 1. 判断当前字符不是 %
        if (*formatString != '%') {

            // 1> 先把当前字符赋值给 str 指向的地址，然后让 str 指向下一个字节的地址
            *str++ = *formatString;

            // 2> 结束本次循环
            continue;

        }

        // 2. 将标志位置为0
        flags = 0;

        // 3. 如果当前字符是 '-'、'+'、' '、'#'、'0' 的话，就会继续跳回来执行
        repeat:
        // 1> 指向下一个地址
        formatString++;

        // 2> 判断是否是 -'、'+'、' '、'#'、'0' 这个几个字符
        switch (*formatString) {

            case '-':
                flags |= SUBTRACT;
                goto repeat;
                break;
            case '+':
                flags |= PLUS;
                goto repeat;
                break;
            case ' ':
                flags |= SPACE;
                goto repeat;
                break;
//                    case '#':
//                        flags |= SPECIAL;
//                        goto repeat;
//                        break;
            case '0':
                flags |= ZEROPAD;
                goto repeat;
                break;

        }

        // 3> 重置属性宽度
        fieldWidth = -1;

        // 4> 判断当前字符是否是数值字母
        if (isDigit(*formatString)) {

            // 因为这是 % 后面的，所以可以肯定是属性的宽度
            fieldWidth = skip_atoi(&formatString);

        } else if (*formatString == '*') {

            formatString++;

            fieldWidth = va_arg(args, int);

            if (fieldWidth < 0) {

                fieldWidth = -fieldWidth;

                flags |= SUBTRACT;

            }

        }

        // 5> 初始化精度
        precision = 1;

        // 6> 判断当前字符是否是 '.'，如果是的话，说明这个字符后面是精度
        if (*formatString == '.') {

            // A、指向下一个字节，因为有可能是精度
            formatString++;

            // B、判断当前字符是不是数值字符
            if (isDigit(*formatString)) {

                // 如果是把数值字符串读出来
                precision = skip_atoi(&formatString);

            } else if (*formatString == '*') {

                formatString++;

                precision = va_arg(args, int);

            }

            // C、判断精度是否大于 0 ，如果小于 0 ，就把精度初始化为 0
            if (precision < 0) {

                precision = 0;

            }

        }

        // 7> 重置修饰符变量
        qualifier = -1;

        // 8> 判断当前字符是不是 ‘h’、'l'、'L'、'Z'，如果是就说明要打印的是整数
        if (*formatString == 'h' || *formatString == 'l' || *formatString == 'L' || *formatString == 'Z') {

            qualifier = *formatString;

            formatString++;

        }

        // 9> 如果上面的都不是的话，就有可能是输出格式字符了
        switch (*formatString) {

            // A、判断是否是要输出一个字符
            case 'c':
                // 判断是否标志变量是否是 '-'，不是才为真
                if (!(flags & SUBTRACT)) {

                    while (--fieldWidth > 0) {
                        *str++ = '0';
                    }

                }

                // 从可变参数获取到一个字节的字符，并保存下来
                *str++ = (unsigned char) va_arg(args, int);

                // 把指定的字符宽度，多的部分变成空格
                while (--fieldWidth > 0) {
                    *str++ = '0';
                }

                break;

                // B、判断是否要输出字符串
            case 's':
                // 从可变参数那里获取到字符串列表
                s = va_arg(args, char *);

                // 如果获取的是错误的
                if (!s) {
                    s = '\0';
                }

                // 取得字符串的长度
                len = strlen(s);

                // 判断精度是否小于 0 和判断精度是否小于字符串的长度
//                if (precision < 0) {
//
//                    precision = len;
//
//                } else if (len > precision) {
//
//                    len = precision;
//
//                }

                // 如果没有 '-'，就给加个空格
                if (!(flags | SUBTRACT)) {
                    while (len < fieldWidth) {
                        *str++ = '0';
                    }
                }

                // 复制字符串到 str
                for (int i = 0; i < len; ++i) {
                    *str++ = *s++;
                }

                // 把对应的宽度变成空格
                while (len < fieldWidth--) {
                    *str++ = '0';
                }

                break;

            case 'o':

                // 判断是不是要输出长整型数值
                if (qualifier == 'l') {
                    str = number(str, va_arg(args, unsigned long), 8, fieldWidth, precision, flags);
                } else {
                    str = number(str, va_arg(args, unsigned int), 8, fieldWidth, precision, flags);
                }

                break;

            case 'p':
                if (fieldWidth == -1) {
                    fieldWidth = 2 * sizeof(void *);
                    flags |= ZEROPAD;
                }

                str = number(str, (unsigned long) va_arg(args, void *), 16, fieldWidth, precision, flags);

                break;

            case 'x':
                flags |= SMALL;
                *str++ = '0';
                *str++ = 'x';
                if (qualifier == 'l') {
                    str = number(str, va_arg(args, unsigned long), 16, fieldWidth, precision, flags);
                } else {
                    str = number(str, va_arg(args, unsigned int), 16, fieldWidth, precision, flags);
                }
                break;

            case 'X':
                *str++ = '0';
                *str++ = 'x';
                if (qualifier == 'l') {
                    str = number(str, va_arg(args, unsigned long), 16, fieldWidth, precision, flags);
                } else {
                    str = number(str, va_arg(args, unsigned int), 16, fieldWidth, precision, flags);
                }
                break;

            case 'd':
                str = number(str, va_arg(args, unsigned int), 10, fieldWidth, precision, flags);
                break;

            case 'i':
                flags |= SIGN;
                break;

            case 'u':
                if (qualifier == 'l') {
                    str = number(str, va_arg(args, unsigned long), 10, fieldWidth, precision, flags);
                } else {
                    str = number(str, va_arg(args, unsigned int), 10, fieldWidth, precision, flags);
                }
                break;

            case 'n':
                if (qualifier == 'l') {
                    long *ip = va_arg(args, long *);
                    *ip = (str - buffer);
                } else {
                    int *ip = va_arg(args, int *);
                    *ip = (long *)(str - buffer);
                }
                break;

            case '%':
                *str++ = '%';
                break;

            default:
                *str++ = '%';
                if (*formatString)
                    *str++ = *formatString;
                else
                    formatString--;
                break;

        }

    }

    *str++ = '\0';

    return (int) (str - buffer);

}

/**
 * 打印一个字符
 * @param fontBufferAddr 字体缓冲区地址
 * @param resolutionX 屏幕分辨率 X
 * @param currentX 当前的X
 * @param currentY 当前的Y
 * @param fontColor 字体颜色
 * @param backgroundColor 要打印的字体的背景颜色
 * @param font 要打印的字体
 */
void printChar(unsigned int *fontBufferAddr, int resolutionX, int currentX,
               int currentY, unsigned int fontColor, unsigned int backgroundColor,
               unsigned char font) {

    // i 是行的循环控制变量， j 是列的循环控制变量
    int i = 0, j = 0;

    // 字符要打印的帧缓存地址
    unsigned int *addr = NULL;

    // 字体点阵
    unsigned char *fontBitmap = NULL;

    // 用来与字符点阵进行位比较的
    unsigned long testval = 0;

    // 1. 获取到字符的点阵，通过 ASCII 值为下标来获取
    fontBitmap = fontASCII[font];

    // 2. 通过循环来给 16 行像素赋值
    for (i = 0; i < 16; i++) {

        // 1> 计算出当前要写入的帧缓存的首地址
        addr = fontBufferAddr + resolutionX * (currentY + i) + currentX;

        // 2> 重置比较位
        testval = 0x80; // 0x80 = 1000 0000 二进制

        // 3> 通过循环来为 16 个像素赋值
        for (j = 0; j < 8; j++) {

            // A、 通过按位并来测试对应为是否为 1
            if (*fontBitmap & testval) {
                *addr = fontColor;
            } else {
                *addr = backgroundColor;
            }

            // B、 testval 右移一位
            testval = testval >> 1;

            // C、 地址往后移 8 个字节
            addr++;

        }

        // 3> 字符位图移 1 个字节
        fontBitmap++;

    }

}